#include "ptwrap.hpp"
#include <deque>
#include <stdio.h>
#include <assert.h>

struct Message 
{
	Message(int mtype) : msgtype_(mtype)
	{ }
	virtual ~Message() { } // allow for polymorphism

	enum {
		EXIT,
		CANCEL,
		NORMAL, // first normal type
		SOME_MSG_1
	};
	int msgtype_;
	// ... whatever else is needed by the application ...
};

// [[ex04da]]
// DEBUG descr The priority messages can be pushed to the front of the queue.
class Queue
{
	// DEBUG {
	// copied from ex04cm
public:
	enum {
		DEFAULT_LIMIT = 1000
	};

	Queue(int limit = DEFAULT_LIMIT) :
		limit_(limit), 
		dqwr_(&deque1_), dqrd_(&deque2_), condrd_(condwr_),
		exitposted_(false), nwriters_(0)
	{ }
	virtual ~Queue()
	{ 
		dispose(deque1_);
		dispose(deque2_);
	}
	virtual bool post(Message *m)
	{
		int res;
		{
			pw::lockmutex lm(mutexx_);
			res = post_l(m);
		}
		if (res == P_POSTIT)
			return push_back(m);
		else
			return (res != P_FAIL);
	}
	Message *pop_front()
	{
		pw::lockmutex lm(mutexrd_);

		while(dqrd_->empty()) {
			{
				pw::lockmutex lmw(condwr_);

				if (!dqwr_->empty()) {
					swap(dqwr_, dqrd_);
					// if the write buffer was full, wake up the writers
					condwr_.broadcast();
				}
			}
			if(dqrd_->empty()) {
				pw::swapmutex sw(mutexrd_, condwr_);

				if (dqwr_->empty())
					condrd_.wait(); 
			}
		}

		Message *m = dqrd_->front();
		dqrd_->pop_front();
		return m;
	}
	// virtual in case if later a subclass wants to do something different;
	// returns true on success, or false if the handler has already been told
	// to exit
	virtual bool add_writer()
	{ 
		pw::lockmutex lm(mutexx_);
		if (exitposted_)
			return false;
		++nwriters_;
		return true;
	}
protected:
	enum post_l_result {
		P_FAIL,
		P_SUCCESS,
		P_POSTIT
	};

	virtual post_l_result post_l(Message *m)
	{
		bool pushlocked = false;
		if (exitposted_) {
			delete m;
			return P_FAIL;
		}
		switch (m->msgtype_) {
		case Message::CANCEL:
			clear(); // then the posting is done as usual...
			pushlocked = true;
			break;
		case Message::EXIT:
			if (--nwriters_ <= 0) {
				// last writer, post EXIT to the handler
				exitposted_ = true;
			} else {
				// message is processed successfully but it has
				// nothing to pass to the handler yet
				delete m;
				return P_SUCCESS;
			}
			pushlocked = true;
			break;
		// ...
		}
		if (pushlocked) {
			if (push_back(m)) // should never fail here, but just in case
				return P_SUCCESS;
			else
				return P_FAIL;
		} else {
			return P_POSTIT;
		}
	}

	// Caller must have the mutex in condwr_ locked.
	bool push_back_l(Message *m)
	{
		if (exitposted_ && m->msgtype_ != Message::EXIT) {
			delete m;
			return false;
		}

		while (dqwr_->size() >= limit_)
			condwr_.wait(); // it releases and gets back the mutex

		dqwr_->push_back(m);
		condrd_.broadcast();
		return true;
	}
	// returns false if the exit was already posted, and this
	// is not the EXIT message itself (then the message also
	// gets discarded)
	bool push_back(Message *m)
	{
		pw::lockmutex lm(condwr_);
		return push_back_l(m);
	}
	void clear() 
	{
		pw::lockmutex lmr(mutexrd_);
		pw::lockmutex lmw(condwr_);

		dispose(*dqrd_);
		dispose(*dqwr_);

		// if the write buffer was full, wake up the writers
		condwr_.broadcast();
	}
	void dispose(std::deque<Message *> &deq)
	{ 
		Message *m;
		while (!deq.empty()) {
			m = deq.front();
			deq.pop_front();
			delete m;
		}
	}

	int limit_;
	std::deque<Message *> deque1_, deque2_;
	pw::pmutex mutexrd_;
	std::deque<Message *> *dqwr_, *dqrd_;
	pw::pmcond condwr_;
	pw::pchaincond condrd_;
	pw::pmutex mutexx_; // for the exit accounting
	bool exitposted_; // whether the EXIT message was posted to the queue
	int nwriters_; // number of writers that haven't already exited
	// DEBUG }
	// ...
protected:
	// ...
	// supposedly, post() will know, for which
	// messages to use push_front()
	void push_front(Message *m)
	{
		pw::lockmutex lm(mutexrd_);

		bool wasempty = dqrd_->empty();
		dqrd_->push_front(m);
		if (wasempty)
			condrd_.broadcastlock();
	}
	// pop_front() is the same as in the last example,
	// releasing mutexrd_ when sleeping on condrd_
	// ...
};
// END

// DEBUG {
class SomeQueue : public Queue
{
public:
	SomeQueue(int limit = DEFAULT_LIMIT) : Queue(limit)
	{ }
	//...
protected:
	virtual post_l_result post_l(Message *m)
	{
		switch (m->msgtype_) {
		case Message::SOME_MSG_1:
			// ... some processing ...
			push_front(m);
			return P_SUCCESS;
		case Message::CANCEL:
			// ... some additional processing ...
			return Queue::post_l(m); // then inherit from base
		default:
			return Queue::post_l(m); // inherit the rest from base
		};
	}
};

struct data : public Message
{
	int v_;

	data(int v, int kind = NORMAL) : Message(kind), v_(v)
	{ }
	~data()
	{
		fprintf(stderr, "deleted data %d\n", v_);
	}
};

int main()
{
	SomeQueue q;
	q.add_writer();
	q.post(new data(1));
	q.post(new data(2, Message::SOME_MSG_1));
	data * v2 = static_cast<data *>(q.pop_front());
	assert(v2->v_ == 2);
	delete v2;
	return 0;
}
/* Sample output:
deleted data 1
deleted data 2
*/
// DEBUG }
